events.js ➔ triggerEvents   C
last analyzed

Complexity

Conditions 11
Paths 10

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 11
c 2
b 0
f 0
nc 10
nop 2
dl 0
loc 34
rs 5.2653

How to fix   Complexity   

Complexity

Complex classes like events.js ➔ triggerEvents often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import $ from 'jquery';
0 ignored issues
show
introduced by
Definition for rule 'keyword-spacing' was not found
Loading history...
2
import _ from 'underscore';
3
import {
4
  Backbone
5
} from './core.js';
6
7
// Backbone.Events
8
// ---------------
9
10
// A module that can be mixed in to *any object* in order to provide it with
11
// a custom event channel. You may bind a callback to an event with `on` or
12
// remove with `off`; `trigger`-ing an event fires all callbacks in
13
// succession.
14
//
15
//     var object = {};
16
//     _.extend(object, Backbone.Events);
17
//     object.on('expand', function(){ alert('expanded'); });
18
//     object.trigger('expand');
19
//
20
var Events = Backbone.Events = {};
21
22
// Regular expression used to split event strings.
23
var eventSplitter = /\s+/;
24
25
// Iterates over the standard `event, callback` (as well as the fancy multiple
26
// space-separated events `"change blur", callback` and jQuery-style event
27
// maps `{event: callback}`).
28
var eventsApi = function (iteratee, events, name, callback, opts) {
29
  var i = 0,
30
    names;
31
  if (name && typeof name === 'object') {
32
    // Handle event maps.
33
    if (callback !== void 0 && 'context' in opts && opts.context === void 0) {
34
      opts.context = callback;
35
    }
36
    for (names = _.keys(name); i < names.length; i++) {
37
      events = eventsApi(iteratee, events, names[i], name[names[i]],
38
        opts);
39
    }
40
  } else if (name && eventSplitter.test(name)) {
41
    // Handle space-separated event names by delegating them individually.
42
    for (names = name.split(eventSplitter); i < names.length; i++) {
43
      events = iteratee(events, names[i], callback, opts);
44
    }
45
  } else {
46
    // Finally, standard events.
47
    events = iteratee(events, name, callback, opts);
48
  }
49
  return events;
50
};
51
52
// The reducing API that adds a callback to the `events` object.
53
var onApi = function (events, name, callback, options) {
54
  if (callback) {
55
    var handlers = events[name] || (events[name] = []);
56
    var context = options.context,
57
      ctx = options.ctx,
58
      listening = options.listening;
59
    if (listening) {
60
      listening.count++;
61
    }
62
63
    handlers.push({
64
      callback: callback,
65
      context: context,
66
      ctx: context || ctx,
67
      listening: listening
68
    });
69
  }
70
  return events;
71
};
72
73
// The reducing API that removes a callback from the `events` object.
74
var offApi = function (events, name, callback, options) {
75
  if (!events) {
76
    return;
77
  }
78
79
  var i = 0,
80
    listening;
81
  var context = options.context,
82
    listeners = options.listeners;
83
84
  // Delete all events listeners and "drop" events.
85
  if (!name && !callback && !context) {
86
    var ids = _.keys(listeners);
87
    for (; i < ids.length; i++) {
88
      listening = listeners[ids[i]];
89
      delete listeners[listening.id];
90
      delete listening.listeningTo[listening.objId];
91
    }
92
    return;
93
  }
94
95
  var names = name ? [name] : _.keys(events);
96
  for (; i < names.length; i++) {
97
    name = names[i];
98
    var handlers = events[name];
99
100
    // Bail out if there are no events stored.
101
    if (!handlers) {
102
      break;
103
    }
104
105
    // Replace events if there are any remaining.  Otherwise, clean up.
106
    var remaining = [];
107
    for (var j = 0; j < handlers.length; j++) {
108
      var handler = handlers[j];
109
      if (
110
        callback && callback !== handler.callback &&
111
        callback !== handler.callback._callback ||
112
        context && context !== handler.context
113
      ) {
114
        remaining.push(handler);
115
      } else {
116
        listening = handler.listening;
117
        if (listening && --listening.count === 0) {
0 ignored issues
show
introduced by
Blocks are nested too deeply (4).
Loading history...
118
          delete listeners[listening.id];
119
          delete listening.listeningTo[listening.objId];
120
        }
121
      }
122
    }
123
124
    // Update tail event if the list has any events.  Otherwise, clean up.
125
    if (remaining.length) {
126
      events[name] = remaining;
127
    } else {
128
      delete events[name];
129
    }
130
  }
131
  return events;
132
};
133
134
// Guard the `listening` argument from the public API.
135
var internalOn = function (obj, name, callback, context, listening) {
136
  obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
137
    context: context,
138
    ctx: obj,
139
    listening: listening
140
  });
141
142
  if (listening) {
143
    var listeners = obj._listeners || (obj._listeners = {});
144
    listeners[listening.id] = listening;
145
  }
146
147
  return obj;
148
};
149
150
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
151
// `offer` unbinds the `onceWrapper` after it has been called.
152
var onceMap = function (map, name, callback, offer) {
153
  if (callback) {
154
    var once = map[name] = _.once(function () {
155
      offer(name, once);
156
      callback.apply(this, arguments);
157
    });
158
    once._callback = callback;
159
  }
160
  return map;
161
};
162
163
// Handles triggering the appropriate event callbacks.
164
var triggerApi = function (objEvents, name, callback, args) {
165
  if (objEvents) {
166
    var events = objEvents[name];
167
    var allEvents = objEvents.all;
168
    if (events && allEvents) {
169
      allEvents = allEvents.slice();
170
    }
171
    if (events) {
172
      triggerEvents(events, args);
0 ignored issues
show
introduced by
"triggerEvents" was used before it was defined
Loading history...
173
    }
174
    if (allEvents) {
175
      triggerEvents(allEvents, [name].concat(args));
0 ignored issues
show
introduced by
"triggerEvents" was used before it was defined
Loading history...
176
    }
177
  }
178
  return objEvents;
179
};
180
181
// A difficult-to-believe, but optimized internal dispatch function for
182
// triggering events. Tries to keep the usual cases speedy (most internal
183
// Backbone events have 3 arguments).
184
var triggerEvents = function (events, args) {
185
  var ev, i = -1,
186
    l = events.length,
187
    a1 = args[0],
188
    a2 = args[1],
189
    a3 = args[2];
190
  switch (args.length) {
191
  case 0:
192
    while (++i < l) {
193
      (ev = events[i]).callback.call(ev.ctx);
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
194
    }
195
    return;
196
  case 1:
197
    while (++i < l) {
198
      (ev = events[i]).callback.call(ev.ctx, a1);
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
199
    }
200
    return;
201
  case 2:
202
    while (++i < l) {
203
      (ev = events[i]).callback.call(ev.ctx, a1, a2);
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
204
    }
205
    return;
206
  case 3:
207
    while (++i < l) {
208
      (ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
209
    }
210
    return;
211
  default:
212
    while (++i < l) {
213
      (ev = events[i]).callback.apply(ev.ctx, args);
0 ignored issues
show
introduced by
This node falls through to the next case due to this statement. Please add a comment either directly below this line or between the cases to explain.
Loading history...
214
    }
215
    return;
216
  }
217
};
218
219
// Bind an event to a `callback` function. Passing `"all"` will bind
220
// the callback to all events fired.
221
Events.on = function (name, callback, context) {
222
  return internalOn(this, name, callback, context);
223
};
224
225
// Inversion-of-control versions of `on`. Tell *this* object to listen to
226
// an event in another object... keeping track of what it's listening to
227
// for easier unbinding later.
228
Events.listenTo = function (obj, name, callback) {
229
  if (!obj) {
230
    return this;
231
  }
232
  var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
233
  var listeningTo = this._listeningTo || (this._listeningTo = {});
234
  var listening = listeningTo[id];
235
236
  // This object is not listening to any other events on `obj` yet.
237
  // Setup the necessary references to track the listening callbacks.
238
  if (!listening) {
239
    var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
240
    listening = listeningTo[id] = {
241
      obj: obj,
242
      objId: id,
243
      id: thisId,
244
      listeningTo: listeningTo,
245
      count: 0
246
    };
247
  }
248
249
  // Bind callbacks on obj, and keep track of them on listening.
250
  internalOn(obj, name, callback, this, listening);
251
  return this;
252
};
253
254
// Remove one or many callbacks. If `context` is null, removes all
255
// callbacks with that function. If `callback` is null, removes all
256
// callbacks for the event. If `name` is null, removes all bound
257
// callbacks for all events.
258
Events.off = function (name, callback, context) {
259
  if (!this._events) {
260
    return this;
261
  }
262
  this._events = eventsApi(offApi, this._events, name, callback, {
263
    context: context,
264
    listeners: this._listeners
265
  });
266
  return this;
267
};
268
269
// Tell this object to stop listening to either specific events ... or
270
// to every object it's currently listening to.
271
Events.stopListening = function (obj, name, callback) {
272
  var listeningTo = this._listeningTo;
273
  if (!listeningTo) {
274
    return this;
275
  }
276
277
  var ids = obj ? [obj._listenId] : _.keys(listeningTo);
278
279
  for (var i = 0; i < ids.length; i++) {
280
    var listening = listeningTo[ids[i]];
281
282
    // If listening doesn't exist, this object is not currently
283
    // listening to obj. Break out early.
284
    if (!listening) {
285
      break;
286
    }
287
288
    listening.obj.off(name, callback, this);
289
  }
290
291
  return this;
292
};
293
294
// Bind an event to only be triggered a single time. After the first time
295
// the callback is invoked, its listener will be removed. If multiple events
296
// are passed in using the space-separated syntax, the handler will fire
297
// once for each event, not once for a combination of all events.
298
Events.once = function (name, callback, context) {
299
  // Map the event into a `{event: once}` object.
300
  var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off,
301
    this));
302
  if (typeof name === 'string' && context == null) {
303
    callback = void 0;
304
  }
305
  return this.on(events, callback, context);
306
};
307
308
// Inversion-of-control versions of `once`.
309
Events.listenToOnce = function (obj, name, callback) {
310
  // Map the event into a `{event: once}` object.
311
  var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening,
312
    this, obj));
313
  return this.listenTo(obj, events);
314
};
315
316
// Trigger one or many events, firing all bound callbacks. Callbacks are
317
// passed the same arguments as `trigger` is, apart from the event name
318
// (unless you're listening on `"all"`, which will cause your callback to
319
// receive the true name of the event as the first argument).
320
Events.trigger = function (name) {
321
  if (!this._events) {
322
    return this;
323
  }
324
325
  var length = Math.max(0, arguments.length - 1);
326
  var args = Array(length);
327
  for (var i = 0; i < length; i++) {
328
    args[i] = arguments[i + 1];
329
  }
330
331
  eventsApi(triggerApi, this._events, name, void 0, args);
332
  return this;
333
};
334
335
// Aliases for backwards compatibility.
336
Events.bind = Events.on;
337
Events.unbind = Events.off;
338
339
export {
340
  Events
341
};
342